/*
 * @(#)HillClimbingB.java 1.0 14/11/13
 * 
 * Clase Compartida
 * PROP Grupo 13 Cluster 3
 */
package dominio;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;

/**
 * Created by Rafael Simon Garcia on 11/14/13.
 */
public class HillClimbingB extends OptimizationAlgorithm{

    //Setup method that receives the the original nodes list
    public static void Setup(ArrayList <? extends GraphElement> nodes){
        nodesList = nodes;
    }

    //Setup method that receives the the original nodes list and the max amount of iterations
    public static void Setup(ArrayList <? extends GraphElement> nodes, int iterations){
        nodesList = nodes;
        maxIterations = iterations;
    }

    public static void Setup(ArrayList <? extends GraphElement> nodes, int iterations, boolean operator){
        nodesList = nodes;
        maxIterations = iterations;
        operatorType = operator;
    }

    //Wrapper that packs the results from performing Hillclimb with random restarts into a PathOptimizationResult

    public static PathOptimizationResult OptimizePath(){
        if (nodesList.size() == 0) return null; // error escaping

        ArrayList<Integer> bestTour;
        ArrayList<HillClimbingResult> results = new ArrayList<>();

        bestTour = DefaultTour(nodesList.size());

        try{
            Class op = operatorType ? SwitchTasksOperator.class : ReverseSectionsOperator.class;
            //get the results
            results = PerformHillClimbWithRandomRestart(bestTour, op, HillClimbFitnessCalculator.class);
        }catch (Exception e){}
        finally {
            PathOptimizationResult pathOptResult;
            //if the results ArrayList is empty return null, otherwise wrap the results and the rearranged node list
            //into a PathOptimizationResults
            pathOptResult = (results.isEmpty()) ? null : new PathOptimizationResult(results, RearrangeNodesFromTour(results.get(0).tour));

            return pathOptResult;
        }
    }

    private static HillClimbingResult PerformHillClimb(ArrayList<Integer> bestTour, Class op, Class c, int maxIters, boolean randomRestarts) throws Exception{
        ArrayList<Integer> nextTour;
        boolean alteredTour;
        int bestTourCost, nextTourCost, iterations = 0;

        //get and call the initialization method on the operator to use
        op.getDeclaredMethod("InitializeOperator", Integer.TYPE).invoke(null, bestTour.size());

        SetupAlgorithmReflection(op, c);

        //if we're using random restarts shuffle the bestTour
        if(randomRestarts) Collections.shuffle(bestTour);

        //get the bestTour length
        bestTourCost = (Integer)Calculate.invoke(null, bestTour, nodesList);

        //while I still have iterations available
        while (iterations < maxIters){
            //set my local maxima found flag to false
            //if after running through all the permutations of the operator I still haven't found a new
            //best tour then break the iterations cycle. it means we're stuck on a local maxima
            alteredTour = false;


            //iterate over the permutations of the operator
            while((Boolean)HasNextValue.invoke(null)){
                //get the next tour
                nextTour = (ArrayList<Integer>)Operate.invoke(null, bestTour);

                if (iterations >= maxIters) break;

                nextTourCost = (Integer)Calculate.invoke(null, nextTour, nodesList); //calculate nextTour length

                iterations += 1; //increase my iterations

                if(nextTour.isEmpty()) continue; //empty nextTour possible due to i = j on the permutations iterator

                //asign best tour to new tour if it's the new best
                if(nextTourCost > bestTourCost){
                    bestTour = nextTour;
                    bestTourCost = nextTourCost;
                    alteredTour = true;
                    break;
                }
            }

            op.getDeclaredMethod("Reset",null).invoke(null); //reset the operator if it needs to be reset

            if(!alteredTour) //break due to local maxima
                break;
        }
        return new HillClimbingResult(iterations, bestTourCost, bestTour);
    }

    private static ArrayList<HillClimbingResult> PerformHillClimbWithRandomRestart(ArrayList<Integer> bestTour, Class op, Class c) throws Exception{
        ArrayList<HillClimbingResult> results = new ArrayList<>();
        HillClimbingResult nextResult, bestResult;
        boolean firstRun = false;
        int remainingIterations, currentIteration = 0;

        // get the method to calculate the best result score
        Method Calculate = c.getDeclaredMethod("Calculate", new Class[] { ArrayList.class, ArrayList.class });

        //set the new bestResult
        bestResult = new HillClimbingResult(0, ((Integer)Calculate.invoke(null, bestTour, nodesList)), bestTour);

        results.add(bestResult);

        //while I still have iterations available
        while(currentIteration < maxIterations ){
            //calculate the remaining iterations to bound the next simple hillclimb
            remainingIterations = maxIterations - currentIteration;

            //get the next result
            nextResult = PerformHillClimb(bestTour, op,
                    c, remainingIterations, firstRun);

            firstRun = true; //set the randomRestarts flag for simple hillclimb to true after the 1st run

            results.add(nextResult);

            //add the executed iterations from simple HC to the currentIteration
            currentIteration += nextResult.iterations;

            //reassign the best result to the latest result if the latter is better than
            //the previous best
            if(nextResult.IsBetter(bestResult)){ bestResult = nextResult; }
        }

        // putting the best result at the front of the returned results,
        // original evaluation is the 2nd item.
        results.remove(bestResult);
        results.add(0, bestResult);

        return results;
    }
}
